package com.todo.auth.conf;

import cn.hutool.json.JSONUtil;
import com.todo.auth.service.StaffDetailsServiceImpl;
import com.todo.auth.tool.TokenService;
import com.todo.common.common.constants.Constants;
import com.todo.common.common.entity.CurrentStaff;
import com.todo.common.common.entity.CurrentUser;
import com.todo.common.common.entity.response.BizResponse;
import com.todo.common.utils.ServletHolderUtil;
import com.todo.common.utils.jackson.JacksonUtil;
import com.todo.component.redis.RedisService;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.web.filter.CorsFilter;

import javax.annotation.Resource;

/**
 * 主要配置登录流程逻辑涉及以下几个类
 *
 * @author azhebuxing
 */
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig {
    private static final Logger log = LoggerFactory.getLogger(SecurityConfig.class);
    /**
     * token认证过滤器
     */
    @Resource
    private JwtAuthenticationTokenFilter jwtTokenFilter;

    @Resource
    private TokenService tokenService;

    @Resource
    private UserDetailsService userDetailsService;
    @Resource
    private RedisService redisService;

    /**
     * 跨域过滤器
     */
    @Resource
    private CorsFilter corsFilter;


    /**
     * 登录异常处理类
     * 用户未登陆的话  在这个Bean中处理
     */
    @Bean
    public AuthenticationEntryPoint customAuthenticationEntryPoint() {
        return (request, response, exception) -> {
            BizResponse<Void> responseDTO = BizResponse.operationFailed("登录异常，用户登录失败，地址：" + request.getRequestURI());
            ServletHolderUtil.renderString(response, JacksonUtil.to(responseDTO));
        };
    }


    /**
     * 登录异常处理类
     * 用户未登陆的话  在这个Bean中处理
     */
    @Bean
    public AuthenticationEntryPoint unauthorizedHandler() {
        return (request, response, exception) -> {
            BizResponse<Object> responseDTO = BizResponse.operationFailed("登录异常，用户权限校验失败，地址：" + request.getRequestURI());
            log.error("unauthorizedHandler 登录异常，用户权限校验失败，地址：{}", request.getRequestURI());
            ServletHolderUtil.renderString(response, JSONUtil.toJsonStr(responseDTO));
        };
    }


    /**
     * 退出成功处理类 返回成功
     * 在SecurityConfig类当中 定义了/logout 路径对应处理逻辑
     */
    @Bean
    public LogoutSuccessHandler logOutSuccessHandler() {
        return (request, response, authentication) -> {
            Boolean todoUser = tokenService.isTodoUser(request);
            if (!todoUser) {
                //员工staff情况
                CurrentStaff currentStaff = tokenService.getCurrentStaff(request);
                if (currentStaff != null) {
                    String userName = currentStaff.getUsername();
                    // 删除用户缓存记录
                    String logKey = String.format(Constants.LOGIN_STAFF_KEY, userName);
                    redisService.del(logKey);
                    // TODO 记录用户退出日志

                }
                ServletHolderUtil.renderString(response, JSONUtil.toJsonStr(BizResponse.success()));
            } else {
                //C端用户情况
                CurrentUser currentUser = tokenService.getCurrentUser(request);
                if (currentUser != null) {
                    String username = currentUser.getUserName();
                    String logKey = String.format(Constants.LOGIN_USER_KEY, username);
                    redisService.del(logKey);
                    // TODO 记录用户退出日志
                }
            }
        };
    }

    /**
     * 强散列哈希加密实现
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }


    /**
     * 鉴权管理类
     *
     * @see StaffDetailsServiceImpl#loadUserByUsername
     */
    @Bean
    public AuthenticationManager authManager(HttpSecurity http) throws Exception {
        return http.getSharedObject(AuthenticationManagerBuilder.class)
                .userDetailsService(userDetailsService)
                .passwordEncoder(bCryptPasswordEncoder())
                .and()
                .build();
    }


    @Bean
    public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                // CSRF禁用，因为不使用session
                .csrf().disable()
                // 认证失败处理类
                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler()).and()
                // 基于token，所以不需要session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                // 过滤请求
                .authorizeRequests()
                // 对于登录login 注册register 验证码captchaImage 以及公共Api的请求允许匿名访问
                // 注意： 当携带token请求以下这几个接口时 会返回403的错误
                //后台登录注册
                .antMatchers("/auth/login", "/register", "/getConfig", "/auth/captchaImage", "/api/**").anonymous()
                //前台登录注册
                .antMatchers("/auth/userAccount/userNameRegister", "/auth/userAccount/userNameLogin").anonymous()
                //服务端之间调用
                .antMatchers("/api/**").anonymous()
                .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js",
                        "/profile/**").permitAll()
                // TODO this is danger.
                //字典不做鉴权
                //.antMatchers("/tool/dict/**").anonymous()
                .antMatchers("/swagger-ui/**", "swagger-ui.index").anonymous()
                .antMatchers("/swagger-resources/**").anonymous()
                .antMatchers("/webjars/**").anonymous()
                .antMatchers("/*/api-docs/**", "/*/api-docs/swagger-config").anonymous()
                .antMatchers("/**/api-docs.yaml").anonymous()
                .antMatchers("/druid/**").anonymous()
                .antMatchers("/webjars/**").anonymous()
                .antMatchers("/doc.html#/**", "doc.html").anonymous()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated()
                .and()
                // 禁用 X-Frame-Options 响应头。下面是具体解释：
                // X-Frame-Options 是一个 HTTP 响应头，用于防止网页被嵌入到其他网页的 <frame>、<iframe> 或 <object> 标签中，从而可以减少点击劫持攻击的风险
                .headers().frameOptions().disable();
        httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logOutSuccessHandler());
        // 添加JWT filter   需要一开始就通过token识别出登录用户 并放到上下文中   所以jwtFilter需要放前面
        httpSecurity.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);
        // 添加CORS filter
        httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
        httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);

        return httpSecurity.build();
    }


}
